Total-order planner - #645
Conversation
…-order planning. Also included air cargo, spare_tire, blocks world and sussman anomaly examples.
… from FolKB. Changed blocks_world function name to three_block_tower. Cleaned up imports and PlanningKB __repr__ function.
|
This looks really nice -- great work! |
|
There are too many conflicts for me to resolve -- can you have a look? |
|
One of the main issues is that the PlanningKB items are immutable (and therefore hashable and searchable) and the FOLKB items from logic.py aren't. I'll need some time to see if I can make these two object models work together, and probably a decision from you as to which way to go with the implementation. |
|
Hey @bcorfman, I know that |
|
Hi Dr. Norvig - I use the existing A* algorithm to solve the example planning problems. Recall that the set of explored items is maintained during the search to keep track of repeated states (line 270 in the current search.py). Any items in this set must be hashable and unique, or they will fail when they are added to the set. I use my PlanningKB as the state variable in the search Node, and it's hashable because the PlanningKB only uses frozenset variables instead of regular mutable sets. The FolKB database doesn't work as the Node.state because it's designed (because of its tell and ask methods) to be mutable -- that means its clauses variable is a mutable list, and its state can't be hashed. |
|
I got it. What about if you use |
|
Yep, a tuple should work for the FolKB clauses. (I remember using tuples with the Node setup for one of the Berkeley Pac-Man projects.) I guess my question is whether you want to a) just make the FolKB usable for the search algorithms, 2) combine the existing logic.py and my new planning.py code together, or 3) just pass the FolKB clause list into the PlanningKB init and then use the PlanningKB to conduct the search. I think number 3 is the easiest -- although there'd be two KBs for different purposes, my PlanningKB and Action classes use frozenset methods to quickly perform goal and precondition checks. I can make changes based on what you think is the best long-term approach. |
| node = frontier.pop() | ||
| if problem.goal_test(node.state): | ||
| return node | ||
| yield node |
There was a problem hiding this comment.
No, you can't change the search algorithms to be generators instead of functions. You'll need to define a separate function if you want to yield all possible solutions.
There was a problem hiding this comment.
Thanks for the tip. I will revert this in my next push.
method can be used. Changed tokenize() to add whitespace around colons, so that actions parse correctly.
Working on problem file parsing.
move this to a subfolder eventually.
line/col info for error reporting. Didn't pan out because of carrying the line/col info in a separate data structure that makes it hard to keep in sync, plus it just seems like overkill.
Added shoes domain and problem.
Fixed bug on build_expr_string to correctly capitalize negative Exprs. Created classmethods on PlanningProblem and PlanningAction to __init__ those classes from existing PDDL and Action objects. Fixed __repr__ method on PlanningAction.
|
I also added a couple of classmethods to my PlanningProblem and PlanningAction classes. These alternative constructors (PlanningProblem.from_PDDL_object and PlanningAction.from_action) take existing PDDL and Action objects as parameters in order to convert the KB and actions to be searchable. If you decide to use my PDDL parser too, I might suggest renaming the PDDL class to something more appropriate -- there's starting to be a lot of things named "PDDL" in the code, and it could create confusion. |
| precond = [expr('At(c, a)'), expr('At(p, a)'), expr('Cargo(c)'), expr('Plane(p)'), expr('Airport(a)')] | ||
| effect = [expr('In(c, p)'), expr('~At(c, a)')] | ||
| load = Action(expr('Load(c, p, a)'), precond, effect) | ||
| load = PlanningAction(expr('Load(c, p, a)'), precond, effect) |
There was a problem hiding this comment.
I think it would be better if PlanningAction accepted strings or Exprs, and then this could be
load = PlanningAction('Load(c, p, a)',
'At(c, a), At(p, a), Cargo(c), Plane(p), Airport(a)',
'In(c, p), ~At(c, a)')
and similarly elsewhere.
|
I think this looks pretty good, but there's a lot here stretching over a long time. Can you, @bcorfman , check in with @ad71 and perhaps @samagra14 over in aima-java, who is also working on planning now, and together come to a consensus? |
|
After I checked in, I found a bug that was exposed by one of my test cases, and I realized that the existing Action and PDDL classes were doing some things I should incorporate to fix it. I will make the changes over the next couple days so we're more in sync, and try to come to consensus on the remainder! |
|
@bcorfman please also have a look at #927 and tell me if you want me to change something. My total order planner is a misnomer (and can probably be handled by something as simple as a topological sort on a planning graph) and I'll get rid of it in the next commit but apart from that, the PR heavily relies on the current Action and PDDL classes. It might be a problem if those classes are changed. Do inform me if you really want to though, we can find a way around it. You can also have a look at the planning notebook to see how things are handled now. It's quite different from the time this pull request was first opened. Thanks! |
|
@ad71 I will take a look at #927 and get my bearings ... the basic issue for using any of the search algorithms (A*) for the current KBs is that they must be frozen types (i.e., tuples, frozensets, etc.) to be hashable when stored in the frontier. If we get away from a search representation for the planners (i.e., like you did with Graphplan, correct?), we can stay with the tell() and retract() methods that modify the KB. But otherwise, it seems like we'd be stuck with two different KB representations: frozen and searchable, or mutable and unsearchable. We could move between them, but I don't think we can combine the KB types together in my opinion (at least without an unfortunate breaking of the Python object model, e.g., utils.hashabledict). I always could be missing something though. |
|
@bcorfman we faced similar problems trying to make |
whether action is valid (separate from one or more valid substitutions).
|
Sorry I'm so behind -- I fixed the bug I found with my TO planner. I will take a look at #927 over the next couple days and see if we can wrap this up ASAP. |
use strings or Exprs for a simplified API.
|
@ad71 I implemented what Dr. @norvig suggested for PlanningAction and PlanningKB ... both of their init methods take strings or Exprs now for a cleaner API. I agree with you on translating between representations ... your observation is a good reason why Python has both lists and tuples and sets and frozensets, and they can easily be moved between. Do you think we should try the classmethod approach for moving between the types, or use some other way like a standalone function? Probably my PlanningProblem should be renamed PlanningSearchProblem or something else to distinguish it from the mutable (PDDL) representations. Same for PlanningAction, so it isn't confused with the existing Action class ... I suggest STRIPSAction because of its negative and positive preconditions and add and remove effects that are implemented with set operations. I'm open to improved naming. As I mentioned previously, strongly think we should rename the PDDL class though (since it has little to do with the PDDL language) ... could we rename that to PlanningProblem instead? |
|
@bcorfman I agree that |
|
OK, I'll change the code to reflect the new names. Should I add a draft writeup on the new code in the Jupiter notebook, or hold off for now?
|
|
Editing notebooks will result in merge conflicts with the other PR. You can add the write-ups later. |
|
No problem; I can wait. |
|
During the interim, I'll work on some unit tests to add to test_planning.py as well. |
Added substitution in PlanningSearchProblem.actions() for valid actions. Added __eq__ special method for comparing STRIPSAction objects, both for correctness and simpler unit testing. Simplified print_solution() function since argument substitution went away above. Changed test_planning_solutions() function to run_planning_solutions() so it wouldn't get triggered by py.test.
since it was causing failures.
|
Thanks @bcorfman. planning.py already provides a total-order solution for planning graphs, so this 2017 PR is superseded; it also includes IDE config files (.idea/) and touches 23 files, which isn't mergeable as-is. Closing — thanks for the contribution! |
Submitting code for planner.